#include "Wt.h"

/* DB session type */

struct Tcl_ObjType WtDbSessionType =
{
  "dbSession",
  WtDbSessionFree,
  WtDbSessionDup,
  WtDbSessionUpdateString,
  WtDbSessionSetFromAny
};

/* Create a new DB session */

Tcl_Obj *WtNewDbSessionObj()
{
  Tcl_Obj *obj = Tcl_NewObj();
  WtDbSession *ds = (WtDbSession *)ckalloc(sizeof(WtDbSession));

  ds->id = WtNewString(NULL);
  Tcl_IncrRefCount(ds->id);

  ds->dbDriver = WtNewString(NULL);
  Tcl_IncrRefCount(ds->dbDriver);

  ds->items = Tcl_NewListObj(0, NULL);
  Tcl_IncrRefCount(ds->items);

  ds->removals = Tcl_NewListObj(0, NULL);
  Tcl_IncrRefCount(ds->removals);

  ds->additions = Tcl_NewListObj(0, NULL);
  Tcl_IncrRefCount(ds->additions);

  ds->cleared = 0;

  ds->fetched = 0;

  obj->internalRep.twoPtrValue.ptr1 = ds;
  obj->typePtr = &WtDbSessionType;
  Tcl_InvalidateStringRep(obj);

  return obj;
}

/* Get the internal rep */

WtDbSession *WtDbSessionRep(Tcl_Obj *obj)
{
  return (WtDbSession *)obj->internalRep.twoPtrValue.ptr1;
}

/* Copy a cookie */

static void WtDbSessionDup(Tcl_Obj *src, Tcl_Obj *dst)
{
  WtDbSession *srcRep = WtDbSessionRep(src);
  WtDbSession *dstRep = (WtDbSession *)ckalloc(sizeof(WtDbSession));

  dstRep->id = srcRep->id;
  Tcl_IncrRefCount(dstRep->id);

  dstRep->dbDriver = srcRep->dbDriver;
  Tcl_IncrRefCount(dstRep->dbDriver);

  dstRep->items = srcRep->items;
  Tcl_IncrRefCount(dstRep->items);

  dstRep->removals = srcRep->removals;
  Tcl_IncrRefCount(dstRep->removals);

  dstRep->additions = srcRep->additions;
  Tcl_IncrRefCount(dstRep->additions);

  dstRep->cleared = dstRep->cleared;

  dstRep->fetched = dstRep->fetched;

  dst->internalRep.twoPtrValue.ptr1 = dstRep;
  dst->typePtr = &WtDbSessionType;
}

/* Update the string rep */

static void WtDbSessionUpdateString(Tcl_Obj *obj)
{
  WtUpdatePtrString(obj);
}

/* Convert an obj to a DB session */

static int WtDbSessionSetFromAny(Tcl_Interp *interp, Tcl_Obj *obj)
{
  return WtSetPtrFromAny(interp, obj, &WtDbSessionType);
}

/* Free a DB session */

static void WtDbSessionFree(Tcl_Obj *obj)
{
  WtDeletePtr(obj);

  WtDbSession *ds = WtDbSessionRep(obj);
  Tcl_DecrRefCount(ds->id);
  Tcl_DecrRefCount(ds->dbDriver);
  Tcl_DecrRefCount(ds->items);
  Tcl_DecrRefCount(ds->removals);
  Tcl_DecrRefCount(ds->additions);

  ckfree((char *)(ds));
}

/* Invoke db driver */

int WtDbSessionDriver(WtDbSession *ds, const char *method, Tcl_Interp *interp,
    int objc, Tcl_Obj *objv[])
{
  int ret, i;
  Tcl_Obj **args, *proc;

  proc = Tcl_NewStringObj(NULL, 0);
  Tcl_AppendObjToObj(proc, ds->dbDriver);
  Tcl_AppendToObj(proc, method, -1);

  args = (Tcl_Obj **)ckalloc((sizeof Tcl_Obj *) * objc + 1);
  args[0] = proc;
  memcpy(args + 1, objv, objc * (sizeof Tcl_Obj *));

  for (i = 0; i < objc + 1; i++) {
    Tcl_IncrRefCount(args[i]);
  }

  ret = Tcl_EvalObjv(interp, objc + 2, args, TCL_EVAL_DIRECT);

  for (i = 0; i < objc + 1; i++) {
    Tcl_DecrRefCount(args[i]);
  }

  ckfree(args);

  return ret;
}

/* Get the session id */

Tcl_Obj *WtGetDbSessionId(WtDbSession *ds, Tcl_Interp *interp)
{
  Tcl_Obj *ret = NULL, *id, *obj;
  WtContext *w = WtGetAssocContext(interp);
  WtSession *ws = WtGetAssocSession(interp);
  int rc;

  if (Tcl_GetCharLength(ds->id)) {
    return ds->id;
  }

  id = WtTableGetObjFromStr(w->response.cookies,
                            ws->cookieName, NULL);

  if (id != NULL && Tcl_GetCharLength(id)) {
    ret = id;
  } else {
    id = WtGenerateDbSessionId(session, interp);
    if (id) {
      rc = WtDbSessionDriver(ds, "create", interp, 1, &id);
      if (rc == TCL_OK) {
        obj = Tcl_GetObjResult(interp);
        if (obj != NULL && Tcl_GetCharLength(obj)) {
          ret = obj;
        } else {
          ret = id;
        }
        Tcl_IncrRefCount(ret);
        Tcl_ResetResult(interp);
      }
    }
  }

  if (ret) {
    Tcl_DecrRefCount(ds->id);
    ds->id = ret;
    Tcl_IncrRefCount(ds->id);
    Tcl_DecrRefCount(ret);
  }

  return ret;
}

/* Set the session id */

void WtSetDbSessionId(WtDbSession *ds, Tcl_Obj *newId, Tcl_Interp *interp)
{
  int ok = 1;
  WtContext *ws = WtGetAssocSession(interp);

  if (strcmp(Tcl_GetString(newId), ds->id)) {
    if (Tcl_GetCharLength(ds->id)) {
      if (!WtCommitDbSession(ds, interp)) {
        ok = 0;
      } else {
        WtClearDbSessionState(ds);
      }
    }
    if (ok) {
      Tcl_DecrRefCount(ds->id);
      ds->id = newId;
      Tcl_IncrRefCount(ds->id);
    }
  }

  return ok;
}

/* Get all session entries */

int WtFetchDbSession(WtDbSession *ds, Tcl_Interp *interp)
{
  int ok = 0, rc;
  Tcl_Obj *objv[2];

  objv[0] = WtGetDbSessionId(session, interp);
  objv[1] = session->items;

  rc = WtDbSessionDriver(ds, "fetch", interp, 2, objv);
  if (rc == TCL_OK) {
    ok = 1;
  }

  return ok;
}

/* Is the session modified */

int WtDbSessionModified(WtDbSession *ds)
{
  return ds->cleared || WtTableSize(ds->removals) ||
    WtTableSize(ds->additions);
}

/* Commit the session */

int WtCommitDbSession(WtDbSession *ds, Tcl_Interp *interp)
{
  Tcl_Obj *objv[4];

  objv[0] = WtGetDbSessionId(ds, interp);
  objv[1] = Tcl_NewIntObj(ds->cleared);    /* clearAll */
  objv[2] = ds->removals;                  /* keysRemoved */
  objv[3] = Tcl_NewListObj(0, NULL);       /* entriesRemoved */
  objv[3] = ds->additions;                 /* entriesAdded */

  int ret = WtDbSessionDriver(ds, "commit", interp, 4, objv);

  if (ret == TCL_OK) {
    WtClearSessionState();
  }

  return ret == TCL_OK;
}

/* Clear the cache and the update log. Leave the session id intact. */

void WtClearDbSessionState(WtDbSession *ds)
{
  ds->fetched = 0;
  WtClearTable(ds->items);
  session->cleared = 0;
  WtClearTable(ds->removals);
  WtClearTable(ds->additions);
}

Tcl_Obj *WtGetDbSessionCookie(WtDbSession *ds, Tcl_Interp *interp)
{
  WtSession *ws = WtGetAssocSession(interp);

  if (!ds->cookie) {
    ds->cookie = WtNewCookieObj();
    if (WtCookieSetName(ds->cookie, ws->cookieName, interp)) {
      Tcl_IncrRefCount(ds->cookie);
    } else {
      Tcl_DecrRefCount(ds->cookie);
      ds->cookie = NULL;
    }
  }

  return ds->cookie;
}

/* Set the session cookie */

void WtDbSessionSetCookie(WtDbSession *ds, Tcl_Interp *interp)
{
  Tcl_Obj *prevId, *cookie;
  WtContext *w = WtGetAssocContext(interp);
  WtSession *ws = WtGetAssocSession(interp);

  if (!Tcl_GetCharLength(ds->id)) {
    return;
  }

  prevId = WtTableGet(w->active->request.cookies, ws->cookieName);

  if (!prevId || strcmp(Tcl_GetString(prevId), Tcl_GetString(ds->id))) {
    cookie = WtDbSessionGetcookie();
    WtCookieSetName(cookie, ds->cookieName, interp);
    WtCookieSetValue(cookie, ds->id, interp);

    /* Put the cookie in the cookie collection */

    WtRemoveFromClnByKey(w->active->response.cookies, cookie, -1);
    WtAppendClnItem(w->active->response.cookies, cookie)
  }
}

/* Generate a session id */

/* TODO: Make stronger. */

Tcl_Obj *WtGenerateDbSessionId(WtDbSession *ds, Tcl_Interp *interp)
{
  Tcl_Obj *ret = NULL, *src, *obj;
  struct timeval tv;
  char buf[256], *bytes;
  int len;
  WtContext *w = WtGetAssocContext(interp);

  src = Tcl_NewStringObj(NULL, 0);

  /* Use mod_unique_id */

  if (obj = WtTableGetObjFromStr(w->active->serverVars, "UNIQUE_ID") &&
      Tcl_GetCharLength(obj)) {
    Tcl_AppendObjToObj(src, obj);
  }

  /* Use environment (weak) */

  if (obj = WtTableGetObjFromStr(w->active->serverVars, "REMOTE_ADDR") &&
      Tcl_GetCharLength(obj)) {
    Tcl_AppendObjToObj(src, obj);
  }
  gettimeofday(&tv, NULL);
  w = (WtContext *)Tcl_GetAssocData(interp, "wt::context", NULL);
  ap_sprintf(buf, sizeof(buf), "%ld%ld%ld%ld%x%x%x%c", src, buf,
    getpid(), gettid(), tv.tv_sec, tv.tv_usec,
    session, interp, w->active->request.apReq, buf[tv.tv_usec % sizeof(buf)]);
  Tcl_AppendToObj(src, buf, -1);
  if (w->apServer->server_hostname) {
    Tcl_AppendToObj(src, w->apServer->server_hostname, -1);
  }
  Tcl_AppendStringsToObj(src, __FILE__, __TIME__, "baboo", -1);

  bytes = Tcl_GetStringFromObj(src, &len);
  ret = Tcl_NewStringObj(ap_md5_binary(w->apReq->pool, bytes, len), -1);

  return ret;
}

/* Destroy the session */

int WtDestroySession(WtDbSession *ds, Tcl_Interp *interp)
{
  int ret = TCL_ERROR;
  Tcl_Obj *id = WtGetSessionId(ds, interp);

  if (id) {
    ret = WtDbSessionDriver(ds, "destroy", interp, 1, &id);
    if (ret == TCL_OK) {
      WtClearSessionState(ds);
      Tcl_DecrRefCount(ds->id);
      ds->id = Tcl_NewStringObj(NULL, 0);
      Tcl_IncrRefCount(ds->id);
    }
  }

  return ret;
}

/* Delete stale sessions */

int WtCleanupSessions(WtDbSession *ds, Tcl_Interp *interp)
{
  return WtDbSessionDriver(ds, "cleanup", interp, 1, &session->maxAge);
}

/* Called when the request is ending */

int WtDbSessionOnRequestEnd(WtDbSession *ds, Tcl_Interp *interp)
{
  return WtDbSessionDriver(ds, "requestEnd", interp, 0, NULL);
}

/* Session command */

int WtDbSessionCmd(ClientData clientData, Tcl_Interp *interp,
                 int objc, Tcl_Obj *const objv[])
{
  return WtDbSessionCmdHelper(clientData, interp, &w->session, objc, objv);
}

/* Session command helper */

int WtDbSessionCmdHelper(ClientData clientData, Tcl_Interp *interp,
                       WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  char *subCmd;

  if (objc < 2) {
    WtDbSessionUsage(interp, objv[0]);
    return ret;
  }

  subCmd = Tcl_GetString(objv[1]);

  if (!strcmp(subCmd, "count")) {
    ret = WtDbSessionCountCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "exists")) {
    ret = WtDbSessionExistsCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "get")) {
    ret = WtDbSessionGetCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "set")) {
    ret = WtDbSessionSetCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "add")) {
    ret = WtDbSessionAddCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "setDefault")) {
    ret = WtDbSessionSetDefaultCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "keys")) {
    ret = WtDbSessionKeysCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "values")) {
    ret = WtDbSessionValuesCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "remove")) {
    ret = WtDbSessionRemoveCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "removeList")) {
    ret = WtDbSessionRemoveListCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "array")) {
    ret = WtDbSessionArrayCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "table")) {
    ret = WtDbSessionTableCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "copy")) {
    ret = WtDbSessionCopyCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "clear")) {
    ret = WtDbSessionClearCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "getAll")) {
    ret = WtDbSessionGetAllCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "setAll")) {
    ret = WtDbSessionSetAllCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "addList")) {
    ret = WtDbSessionAddListCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "setAllDefault")) {
    ret = WtDbSessionSetAllDefaultCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "allValues")) {
    ret = WtDbSessionAllValuesCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "listArray")) {
    ret = WtDbSessionListArrayCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "id")) {
    ret = WtDbSessionIdCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "commit")) {
    ret = WtDbSessionCommitCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "modified")) {
    ret = WtDbSessionModifiedCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "clearCache")) {
    ret = WtDbSessionClearCacheCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "cookie")) {
    ret = WtDbSessionCookieCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "cleanup")) {
    ret = WtDbSessionCleanupCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "destroy")) {
    ret = WtDbSessionDestroyCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "driver")) {
    ret = WtDbSessionDriverCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "driverData")) {
    ret = WtDbSessionDriverDataCmd(clientData, interp, ds, objc, objv);
  } else if (!strcmp(subCmd, "driverEval")) {
    ret = WtDbSessionDriverEvalCmd(clientData, interp, ds, objc, objv);
  } else {
    WtDbSessionUsage(interp, objv[0]);
  }

  return ret;
}

/* Session command usage */

WtDbSessionCmdUsage(Tcl_Interp *interp, Tcl_Obj *cmd)
{
  char *cmdStr = Tcl_GetString(cmd);

  Tcl_AppendResult(interp,
    cmdStr, " count\n",
    cmdStr, " exists key\n",
    cmdStr, " get key ?defaultValue?\n",
    cmdStr, " set key value\n",
    cmdStr, " add key value\n",
    cmdStr, " setDefault key value\n",
    cmdStr, " keys\n",
    cmdStr, " values\n",
    cmdStr, " remove key\n",
    cmdStr, " removeList keys\n",
    cmdStr, " array arrayName\n",
    cmdStr, " table\n",
    cmdStr, " copy\n",
    cmdStr, " clear\n",
    cmdStr, " getAll key ?defaultValue?\n",
    cmdStr, " setAll key list\n",
    cmdStr, " addList key list\n",
    cmdStr, " setAllDefault key list\n",
    cmdStr, " allValues\n",
    cmdStr, " listArray arrayName\n",
    NULL);
  }
}

/* count command */

int WtDbSessionCountCmd(ClientData clientData, Tcl_Interp *interp,
                  WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, count;
  Tcl_Obj *obj;

  if (objc != 2) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
    return ret;
  } else {
    if (WtFetchSession(interp, session)) {
      count = WtTableSize(session->items);
      Tcl_SetObjResult(interp, Tcl_NewIntObj(count));
      ret = TCL_OK;
    }
  }

  return ret;
}

/* exists command */

int WtDbSessionExistsCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR,

  if (objc != 3) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]), " key", NULL);
    return ret;
  } else {
    if (WtFetchSession(interp, session)) {
      Tcl_SetObjResult(interp, Tcl_NewBooleanObj(
        WtTableExists(session->items, objv[2])));
      ret = TCL_OK;
    }
  }

  return ret;
}

/* get command */

int WtDbSessionGetCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR,
  Tcl_Obj *obj;

  if (objc != 3 && objc != 4) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]), " key ?defaultValue?", NULL);
    return ret;
  } else {
    if (WtFetchSession(interp, session)) {
      obj = WtTableGet(ds->items, objv[2]);
      if (obj) {
        Tcl_SetObjResult(interp, obj);
        ret = TCL_OK;
      } else if (obc > 3) {
        Tcl_SetObjResult(interp, objv[3]);
        ret = TCL_OK;
      } else {
        Tcl_AppendResult("No entry with key \"", Tcl_GetString(objv[2]),
          "\" exists, and no default value was specified.", NULL);
      }
    }
  }

  return ret;
}

/* set command */

int WtDbSessionSetCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR,

  if (objc != 4) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]), " key value", NULL);
    return ret;
  } else {
    /* Log the update */
    WtTableSet(ds->removals, objv[2], Tcl_NewBooleanObj(1));
    WtTableSet(ds->additions, objv[2], objv[3]);
    /* Update the cache */
    WtTableSet(ds->items, objv[2], objv[3]);
    Tcl_SetObjResult(interp, objv[3]);
    ret = TCL_OK;
  }

  return ret;
}

/* add command */

int WtDbSessionAddCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR,

  if (objc != 4) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]), " key value", NULL);
    return ret;
  } else {
    /* Log the update */
    WtTableSet(ds->additions, objv[2], objv[3]);
    /* Update the cache */
    WtTableAdd(ds->items, objv[2], objv[3]);
    Tcl_SetObjResult(interp, objv[3]);
    ret = TCL_OK;
  }

  return ret;
}

/* setDefault command */

int WtDbSessionSetDefaultCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR,

  if (objc != 4) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]), " key value", NULL);
    return ret;
  } else {
    if (WtFetchSession(interp, session)) {
      obj = WtTableGet(ds->items, objv[2]);
      if (obj) {
        Tcl_SetObjResult(interp, obj);
      } else {
        /* Log the update */
        WtTableSet(ds->removals, objv[2], Tcl_NewBooleanObj(1));
        WtTableSet(ds->additions, objv[2], objv[3]);
        /* Update the cache */
        WtTableSet(ds->items, objv[2], objv[3]);
        Tcl_SetObjResult(interp, objv[3]);
      }
      ret = TCL_OK;
    }
  }

  return ret;
}

/* keys command */

int WtDbSessionKeysCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR,

  if (objc != 2) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else {
    if (WtFetchSession(interp, session)) {
      Tcl_SetObjResult(interp, WtTableKeys(ds->items));
      ret = TCL_OK;
    }
  }

  return ret;
}

/* values command */

int WtDbSessionValuesCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR,

  if (objc != 2) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else {
    if (WtFetchSession(interp, session)) {
      Tcl_SetObjResult(interp, WtTableValues(ds->items));
      ret = TCL_OK;
    }
  }

  return ret;
}

/* remove command */

int WtDbSessionRemoveCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR,

  if (objc != 3) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]), " key", NULL);
  } else {
    /* Log the update */
    WtTableSet(ds->removals, objv[2], Tcl_NewBooleanObj(1));
    WtTableRemove(ds->additions, objv[2]);
    /* Update the cache */
    WtTableRemove(ds->items, objv[2]);
    ret = TCL_OK;
  }

  return ret;
}

/* removeList command */

int WtDbSessionRemoveListCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, i;
  Tcl_Obj *key;

  if (objc != 3) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]), " list", NULL);
  } else {
    if (Tcl_ListObjLength(interp, objv[2], &listLen) != TCL_OK) {
      Tcl_AppendResult(interp,
        "Argument is not a list. Usage: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " list", NULL);
    } else {
      /* Log the update */
      for (i = 0; i < WtGetListSize(objv[2]); i++) {
        key = WtGetListItem(objv[2], i);
        WtTableSet(ds->removals, key, Tcl_NewBooleanObj(1));
        WtTableRemove(ds->additions, key);
      }
      /* Update the cache */
      WtTableRemoveList(ds->items, objv[2]);
      ret = TCL_OK;
    }
  }

  return ret;
}

/* array command */

int WtDbSessionArrayCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR,

  if (objc != 3) {
    Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]), " arrayName",
      NULL);
  } else {
    if (WtFetchSession(interp, session)) {
      if (WtTableToArray(interp, ds->items, objv[2], 0)) {
        ret = TCL_OK;
      }
    }
  }

  return ret;
}

/* table command */

int WtDbSessionTableCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR,

  if (objc != 2) {
    Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else {
    Tcl_SetObjResult(interp, Tcl_DuplicateObj(ds->items));
    ret = TCL_OK;
  }

  return ret;
}

/* clear command */

int WtDbSessionClearCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR,

  if (objc != 2) {
    Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else {
    WtClearSessionState(session);
    ds->cleared = 1;
    ret = TCL_OK;
  }

  return ret;
}

/* getAll command */

int WtDbSessionGetAllCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR,

  if (objc < 3 || objc > 4) {
    Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " key ?defaultValue?", NULL);
  } else {
    if (WtFetchSession(interp, session)) {
      list = WtTableGetAll(ds->items, objv[2]);
      if (list) {
        Tcl_SetObjResult(interp, list);
        ret = TCL_OK;
      } else if (objc > 3) {
        Tcl_SetObjResult(interp, objv[3]);
        ret = TCL_OK;
      } else {
        Tcl_AppendResult(interp, "No entry with key \"",
          WtToString(objv[2]), "\" exists, and no default value was specified.",
          NULL);
      }
    }
  }

  return ret;
}

/* setAll command */

int WtDbSessionSetAllCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR,

  if (objc != 4) {
    Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " key list", NULL);
  } else {
    if (WtFetchSession(interp, session)) {
      if (Tcl_ListObjLength(interp, objv[3], &listLen) != TCL_OK) {
        Tcl_AppendResult(interp, "Value is not a list. Usage: ",
          WtToString(objv[0]), " ", WtToString(objv[1]),
          " key list", NULL);
      } else {
        /* Log the update */
        WtTableSet(ds->removals, objv[2], Tcl_NewBooleanObj(1));
        WtTableSetAll(ds->additions, objv[2], objv[3]);
        /* Update the cache */
        WtTableSetAll(ds->items, objv[2], objv[3]);
        Tcl_SetObjResult(interp, objv[3]);
        ret = TCL_OK;
      }
    }
  }

  return ret;
}

/* addList command */

int WtDbSessionAddListCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR,

  if (objc != 4) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " key list", NULL);
  } else {
    if (Tcl_ListObjLength(interp, objv[3], &listLen) != TCL_OK) {
      Tcl_AppendResult(interp, "Value is not a list. Usage: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " key list", NULL);
    } else {
      WtTableAddList(ds->items, objv[2], objv[3]);
      Tcl_SetObjResult(interp, objv[3]);
      ret = TCL_OK;
    }
  }

  return ret;
}

/* setAllDefault command */

int WtDbSessionSetAllDefaultCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR,

  if (objc != 4) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " key list", NULL);
  } else {
    if (Tcl_ListObjLength(interp, objv[3], &listLen) != TCL_OK) {
      Tcl_AppendResult(interp, "Value is not a list. Usage: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " key list", NULL);
    } else {
      list = WtTableGetAll(tbl, objv[2]);
      if (list) {
        Tcl_SetObjResult(interp, list);
      } else {
        WtTableSetAll(tbl, objv[2], objv[3]);
        Tcl_SetObjResult(interp, objv[3]);
      }
      ret = TCL_OK;
    }
  }

  return ret;
}

/* allValues command */

int WtDbSessionAllValuesCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR,

  if (objc != 2) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else {
    Tcl_SetObjResult(interp, WtTableValues(ds->items));
    ret = TCL_OK;
  }

  return ret;
}

/* listArray command */

int WtDbSessionListArrayCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR,

  if (objc != 3) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " arrayName", NULL);
  } else {
    if (WtTableToArray(interp, ds->items, objv[2], 1)) {
      ret = TCL_OK;
    }
  }

  return ret;
}

/* id command */

int WtDbSessionIdCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, len;
  const char *bytes;

  w = (WtContext *)Tcl_GetAssocData(interp, "wt::context", NULL);

  if (objc != 2 && objc != 3) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " ?id?", NULL);
  } else {
    if (objc == 2) {
      Tcl_SetObjResult(interp, ds->id);
    } else if (objc == 3) {
      if (WtSetSessionId(session, objv[2], interp)) {
        Tcl_SetObjResult(interp, ds->id);
        ret = TCL_OK;
      }
    }
  }

  return ret;
}

/* commit command */

int WtDbSessionCommitCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR,

  if (objc != 2) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else {
    if (WtCommitSession(session, interp)) {
      ret = TCL_OK;
    }
  }

  return ret;
}

/* modified command */

int WtDbSessionModifiedCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else {
    Tcl_SetResult(interp, Tcl_NewBooleanObj(WtDbSessionModified(session)));
    ret = TCL_OK;
  }

  return ret;
}

/* clearCache command */

int WtDbSessionClearCacheCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else {
    WtClearTable(ds->items);
    ds->fetched = 0;
    ret = TCL_OK;
  }

  return ret;
}

/* cleanup command */

int WtDbSessionCleanupCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (WtCleanupSession(session, interp)) {
    ret = TCL_OK;
  }

  return ret;
}

/* destroy command */

int WtDbSessionDestroyCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  return WtDestroySession(session, interp);
}

/* cookie command */

int WtDbSessionCookieCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_OK;
  Tcl_Obj *cookie;

  if (objc != 2 && objc != 3) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " ?cookie?", NULL);
  } else {
    if (objc == 2) {
      cookie = WtGetSessionCookie(session, interp);
      if (cookie) {
        Tcl_SetObjResult(interp, cookie);
        ret = TCL_OK;
      }
    } else if (objc == 3) {
      if (ds->cookie) {
        Tcl_DecrRefCount(ds->cookie);
      }
      ds->cookie = objv[2];
      Tcl_IncrRefCount(ds->cookie);
      Tcl_SetObjResult(interp, ds->cookie);
      ret = TCL_OK;
    }
  }

  return ret;
}

/* driver command */

int WtDbSessionDriverCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2 && objc != 3) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " ?driver?", NULL);
  } else {
    if (objc == 2) {
      Tcl_SetObjResult(interp, ds->dbDriver);
    } else if (objc == 3) {
      Tcl_DecrRefCount(ds->dbDriver);
      ds->dbDriver = objv[2];
      Tcl_IncrRefCount(ds->dbDriver);
      ret = TCL_OK;
    }
  }

  return ret;
}

/* driverEval command */

int WtDbSessionDriverEvalCmd(ClientData clientData, Tcl_Interp *interp,
    WtDbSession *ds, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc < 3) {
    Tcl_AppendResult(interp, "Incorrect usage. Should be: ",
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " method ?parameter? ...", NULL);
  } else {
    ret = WtDbSessionDriver(ds, Tcl_GetString(objv[2]), interp,
      objc - 3, objv + 3);
  }

  return ret;
}

/* Create session commands */

void WtInitDbSessionCommands(Tcl_Interp *interp)
{
  Tcl_CreateObjCommand(interp, "::wt::session::dbSessionHandler", WtDbSessionCmd, NULL, NULL);
}
